#include <memory>
#include <cstring>
#include <cstdlib>
#include "miniutil/lzma/Types.h"
#include "miniutil/lzma/LzmaEnc.h"
#include "miniutil/lzma/LzmaDec.h"
#include "miniutil/compress.h"


/*
    sample usage:
    ...
    miniutil::util::Compress::MemEncode(psrcbuf, psrclen, &pdestbuf, &pdestlen);
    char* returnvle = NULL;
    int returnlen = 0;
    miniutil::util::Compress::MemDecode(pdestbuf, pdestlen, &returnvle, &returnlen);
    ...
*/
namespace miniutil { namespace util
{
    static void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); }
    static void SzFree(void *p, void *address) { p = p; free(address); }
    static ISzAlloc g_Alloc = { SzAlloc, SzFree };

    struct MemoryUnit
    {
        MemoryUnit()
        {
            pbuf = NULL;
            length = 0;
            woffset = 0;
            roffset = 0;
        }

        ~MemoryUnit()
        {
            if(pbuf != NULL)
                delete []pbuf;
        }

        size_t Write(const void* buf, size_t size)
        {
            if(length - woffset < size)
            {
                size_t newlength = woffset+size+1024;
                char* pnewbuf = new char[newlength];
                if(pbuf != NULL)
                {
                    memcpy(pnewbuf, pbuf, woffset);
                    delete []pbuf;
                }
                pbuf = pnewbuf;
                length = newlength;
            }
            memcpy(pbuf+woffset, buf, size);
            woffset += size;
            return size;
        }

        SRes Read(void *buf, size_t *size)
        {
            if(roffset < woffset)
            {
                size_t readsize = (*size)>(woffset-roffset)?(woffset-roffset):(*size);
                memcpy(buf, pbuf+roffset, readsize);
                roffset += readsize;
                (*size) = readsize;
                return (int)SZ_OK;
            }
            (*size) = 0;
            return SZ_OK;
        }

        int ReadAllContent(void** pbufi, size_t* size)
        {
            if(pbuf != NULL)
            {
                (*pbufi) = new char[woffset-roffset];
                (*size) = woffset-roffset;
                memcpy((*pbufi), pbuf+roffset, woffset-roffset);
                roffset = woffset;
                return (int)(*size);
            }
            return 0;
        }

        char* pbuf;
        size_t length;
        size_t woffset;
        size_t roffset;
    };

    //size_t MemoryWrite(void* p, const void* buf, size_t size)
    //{
    //    MemoryUnit* punit = (MemoryUnit*)p;
    //    return punit->Write(buf, size);
    //}

    //SRes MemoryRead(void *p, void *buf, size_t *size)
    //{
    //    MemoryUnit* punit = (MemoryUnit*)p;
    //    return punit->Read(buf, size);
    //}

    //typedef struct
    //{
    //    ISeqInStream s;
    //    MemoryUnit munit;
    //}MemorySeqInStream;

    //typedef struct
    //{
    //    ISeqOutStream s;
    //    MemoryUnit munit;
    //}MemorySeqOutStream;

    size_t MemoryWrite(void* p, const void* buf, size_t size);
    SRes MemoryRead(void *p, void *buf, size_t *size);

    class MemorySeqInStream:public ISeqInStream
    {
        public:
            MemorySeqInStream()
            {
                this->Read = MemoryRead;
            }
            MemoryUnit munit;
    };

    class MemorySeqOutStream:public ISeqOutStream
    {
        public:
            MemorySeqOutStream()
            {
                this->Write = MemoryWrite;
            }
            MemoryUnit munit;
    };

    size_t MemoryWrite(void* p, const void* buf, size_t size)
    {
        MemorySeqOutStream* pstr = (MemorySeqOutStream*)p;
        return pstr->munit.Write(buf, size);
    }

    SRes MemoryRead(void *p, void *buf, size_t *size)
    {
        MemorySeqInStream* pstr = (MemorySeqInStream*)p;
        return pstr->munit.Read(buf, size);
    }


    //srclen must be UInt64, or it will be error!
    int Compress::MemEncode(const char* psrc, unsigned long long srclen, char** pdest, int* pdestlen)
    {
        CLzmaEncHandle enc;
        SRes res;
        CLzmaEncProps props;

        MemorySeqOutStream outstream;
        MemorySeqInStream instream;

        //copy content
        instream.munit.Write(psrc, srclen);

        enc = LzmaEnc_Create(&g_Alloc);
        if (enc == 0)
        return SZ_ERROR_MEM;

        LzmaEncProps_Init(&props);
        res = LzmaEnc_SetProps(enc, &props);

        if (res == SZ_OK)
        {
            Byte header[LZMA_PROPS_SIZE + 8];
            size_t headerSize = LZMA_PROPS_SIZE;
            int i;

            res = LzmaEnc_WriteProperties(enc, header, &headerSize);
            for (i = 0; i < 8; i++)
              header[headerSize++] = (Byte)(srclen >> (8 * i));
            if (outstream.Write(&outstream, header, headerSize) != headerSize)
              res = SZ_ERROR_WRITE;
            else
            {
              if (res == SZ_OK)
                res = LzmaEnc_Encode(enc, &outstream, &instream, NULL, &g_Alloc, &g_Alloc);
            }
        }
        LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc);
        size_t readlen = 0;
        outstream.munit.ReadAllContent((void**)pdest, (size_t*)&readlen);
        (*pdestlen) = (int)readlen;
        return res;
    }

    #define IN_BUF_SIZE (1 << 16)
    #define OUT_BUF_SIZE (1 << 16)

    static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream,
        UInt64 unpackSize)
    {
        int thereIsSize = (unpackSize != (UInt64)(Int64)-1);
        Byte inBuf[IN_BUF_SIZE];
        Byte outBuf[OUT_BUF_SIZE];
        size_t inPos = 0, inSize = 0, outPos = 0;
        LzmaDec_Init(state);
        for (;;)
        {
            if (inPos == inSize)
            {
              inSize = IN_BUF_SIZE;
              RINOK(inStream->Read(inStream, inBuf, &inSize));
              inPos = 0;
            }
            {
                SRes res;
                SizeT inProcessed = inSize - inPos;
                SizeT outProcessed = OUT_BUF_SIZE - outPos;
                ELzmaFinishMode finishMode = LZMA_FINISH_ANY;
                ELzmaStatus status;
                if (thereIsSize && outProcessed > unpackSize)
                {
                    outProcessed = (SizeT)unpackSize;
                    finishMode = LZMA_FINISH_END;
                }
              
                res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed,
                    inBuf + inPos, &inProcessed, finishMode, &status);
                inPos += inProcessed;
                outPos += outProcessed;
                unpackSize -= outProcessed;
              
              if (outStream)
                if (outStream->Write(outStream, outBuf, outPos) != outPos)
                  return SZ_ERROR_WRITE;
                
              outPos = 0;
              
              if (res != SZ_OK || thereIsSize && unpackSize == 0)
                return res;
              
              if (inProcessed == 0 && outProcessed == 0)
              {
                if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK)
                  return SZ_ERROR_DATA;
                return res;
              }
            }
        }
    }

    int Compress::MemDecode(const char* psrc, unsigned int srclen, char** pdest, int* pdestlen)
    {
        UInt64 unpackSize;
        int i;
        SRes res = 0;

        CLzmaDec state;

        MemorySeqOutStream outstream;
        MemorySeqInStream instream;

        //copy content
        instream.munit.Write(psrc, srclen);

        /* header: 5 bytes of LZMA properties and 8 bytes of uncompressed size */
        unsigned char header[LZMA_PROPS_SIZE + 8];

        /* Read and parse header */

        RINOK(SeqInStream_Read(&instream, header, sizeof(header)));

        unpackSize = 0;
        for (i = 0; i < 8; i++)
        unpackSize += (UInt64)header[LZMA_PROPS_SIZE + i] << (i * 8);

        LzmaDec_Construct(&state);
        RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc));
        res = Decode2(&state, &outstream, &instream, unpackSize);
        LzmaDec_Free(&state, &g_Alloc);
        size_t readlen = 0;
        outstream.munit.ReadAllContent((void**)pdest, &readlen);
        (*pdestlen) = (int)readlen;
        return res;
    }

} }
